using System;

namespace UnityEngine.Rendering.Universal.Internal
{
    /// <summary>
    /// Renders a shadow map for the main Light.
    /// </summary>
    public class MainLightShadowCasterPass : ScriptableRenderPass
    {
        private static class MainLightShadowConstantBuffer
        {
            public static int _WorldToShadow;
            public static int _ShadowParams;
            public static int _CascadeShadowSplitSpheres0;
            public static int _CascadeShadowSplitSpheres1;
            public static int _CascadeShadowSplitSpheres2;
            public static int _CascadeShadowSplitSpheres3;
            public static int _CascadeShadowSplitSphereRadii;
            public static int _ShadowOffset0;
            public static int _ShadowOffset1;
            public static int _ShadowOffset2;
            public static int _ShadowOffset3;
            public static int _ShadowmapSize;
        }

        const int k_MaxCascades = 4;
        const int k_ShadowmapBufferBits = 64;//32;
        int m_ShadowmapWidth;
        int m_ShadowmapHeight;
        int m_ShadowCasterCascadesCount;
        bool m_SupportsBoxFilterForShadows;

        RenderTargetHandle m_MainLightShadowmap;
        RenderTexture m_MainLightShadowmapTexture;

        Matrix4x4[] m_MainLightShadowMatrices;
        ShadowSliceData[] m_CascadeSlices;
        Vector4[] m_CascadeSplitDistances;

        ShadowStrategy m_strategy;

        #region 关于 esm shadow
        ComputeShader esmShadowComputeShader;
        int esmShadowExpKernel;
        int esmShadowPrefilterKernel;
        float esmC;
        RenderTargetHandle m_TemporaryShadowMap1_Handle;
        RenderTargetHandle m_TemporaryShadowMap2_Handle;
        RenderTargetHandle m_TemporaryShadowMap3_Handle;
        RenderTargetHandle m_TemporaryShadowMap4_Handle;

        int esmGuassianBlurKernel;
        int esmGuassianBlurKernel2;

        ComputeShader blurUtilsComputeShader;
        int dualBlurDownSampleKernel;
        int dualBlurUpSampleKernel;
        #endregion

        #region 关于vsm shadow
        ComputeShader vsmShadowComputeShader;
        int vsmPrefilterKernel;
        int vsmShadowKernel;
        #endregion

        const string m_ProfilerTag = "Render Main Shadowmap";
        ProfilingSampler m_ProfilingSampler = new ProfilingSampler(m_ProfilerTag);

        public MainLightShadowCasterPass(RenderPassEvent evt,
            ShadowStrategy shadowStrategy,
            ComputeShader esmCS,
            ComputeShader vsmCS,
            ComputeShader blurUtilsCS)
        {
            renderPassEvent = evt;

            m_strategy = shadowStrategy;

            #region 关于 esm shadow
            esmShadowComputeShader = esmCS;
            esmC = 100/*esmConstC*/;
            esmShadowExpKernel = esmShadowComputeShader.FindKernel("ESMShadowExpKernel");
            esmShadowPrefilterKernel = esmShadowComputeShader.FindKernel("ESMShadowPrefilterKernel");
            m_TemporaryShadowMap1_Handle.Init("m_temporaryShadowMap1_Handle");
            m_TemporaryShadowMap2_Handle.Init("m_temporaryShadowMap2_Handle");
            m_TemporaryShadowMap3_Handle.Init("m_TemporaryShadowMap3_Handle");
            m_TemporaryShadowMap4_Handle.Init("m_TemporaryShadowMap4_Handle");

            esmGuassianBlurKernel = esmShadowComputeShader.FindKernel("ESMShadowPrefilter_V1_Kernel");
            esmGuassianBlurKernel2 = esmShadowComputeShader.FindKernel("ESMShadowPrefilter_V2_Kernel");


            blurUtilsComputeShader = blurUtilsCS;
            dualBlurDownSampleKernel = blurUtilsComputeShader.FindKernel("DualBlurDownSample");
            dualBlurUpSampleKernel = blurUtilsComputeShader.FindKernel("DualBlurUpSample");
            #endregion

            #region 关于vsm shadow
            vsmShadowComputeShader = vsmCS;
            vsmShadowKernel = vsmShadowComputeShader.FindKernel("VSMShadowKernel");
            vsmPrefilterKernel = vsmShadowComputeShader.FindKernel("VSMPrefilterKernel");
            #endregion

            m_MainLightShadowMatrices = new Matrix4x4[k_MaxCascades + 1];
            m_CascadeSlices = new ShadowSliceData[k_MaxCascades];
            m_CascadeSplitDistances = new Vector4[k_MaxCascades];

            MainLightShadowConstantBuffer._WorldToShadow = Shader.PropertyToID("_MainLightWorldToShadow");
            MainLightShadowConstantBuffer._ShadowParams = Shader.PropertyToID("_MainLightShadowParams");
            MainLightShadowConstantBuffer._CascadeShadowSplitSpheres0 = Shader.PropertyToID("_CascadeShadowSplitSpheres0");
            MainLightShadowConstantBuffer._CascadeShadowSplitSpheres1 = Shader.PropertyToID("_CascadeShadowSplitSpheres1");
            MainLightShadowConstantBuffer._CascadeShadowSplitSpheres2 = Shader.PropertyToID("_CascadeShadowSplitSpheres2");
            MainLightShadowConstantBuffer._CascadeShadowSplitSpheres3 = Shader.PropertyToID("_CascadeShadowSplitSpheres3");
            MainLightShadowConstantBuffer._CascadeShadowSplitSphereRadii = Shader.PropertyToID("_CascadeShadowSplitSphereRadii");
            MainLightShadowConstantBuffer._ShadowOffset0 = Shader.PropertyToID("_MainLightShadowOffset0");
            MainLightShadowConstantBuffer._ShadowOffset1 = Shader.PropertyToID("_MainLightShadowOffset1");
            MainLightShadowConstantBuffer._ShadowOffset2 = Shader.PropertyToID("_MainLightShadowOffset2");
            MainLightShadowConstantBuffer._ShadowOffset3 = Shader.PropertyToID("_MainLightShadowOffset3");
            MainLightShadowConstantBuffer._ShadowmapSize = Shader.PropertyToID("_MainLightShadowmapSize");

            m_MainLightShadowmap.Init("_MainLightShadowmapTexture");
            m_SupportsBoxFilterForShadows = Application.isMobilePlatform || SystemInfo.graphicsDeviceType == GraphicsDeviceType.Switch;
        }

        public bool Setup(ref RenderingData renderingData)
        {
            if (!renderingData.shadowData.supportsMainLightShadows)
                return false;

            Clear();
            int shadowLightIndex = renderingData.lightData.mainLightIndex;
            if (shadowLightIndex == -1)
                return false;

            VisibleLight shadowLight = renderingData.lightData.visibleLights[shadowLightIndex];
            Light light = shadowLight.light;
            if (light.shadows == LightShadows.None)
                return false;

            if (shadowLight.lightType != LightType.Directional)
            {
                Debug.LogWarning("Only directional lights are supported as main light.");
            }

            Bounds bounds;
            if (!renderingData.cullResults.GetShadowCasterBounds(shadowLightIndex, out bounds))
                return false;

            m_ShadowCasterCascadesCount = renderingData.shadowData.mainLightShadowCascadesCount;

            int shadowResolution = ShadowUtils.GetMaxTileResolutionInAtlas(renderingData.shadowData.mainLightShadowmapWidth,
                renderingData.shadowData.mainLightShadowmapHeight, m_ShadowCasterCascadesCount);
            m_ShadowmapWidth = renderingData.shadowData.mainLightShadowmapWidth;
            m_ShadowmapHeight = (m_ShadowCasterCascadesCount == 2) ?
                renderingData.shadowData.mainLightShadowmapHeight >> 1 :
                renderingData.shadowData.mainLightShadowmapHeight;

            for (int cascadeIndex = 0; cascadeIndex < m_ShadowCasterCascadesCount; ++cascadeIndex)
            {
                bool success = ShadowUtils.ExtractDirectionalLightMatrix(ref renderingData.cullResults, ref renderingData.shadowData,
                    shadowLightIndex, cascadeIndex, m_ShadowmapWidth, m_ShadowmapHeight, shadowResolution, light.shadowNearPlane,
                    out m_CascadeSplitDistances[cascadeIndex], out m_CascadeSlices[cascadeIndex], out m_CascadeSlices[cascadeIndex].viewMatrix, out m_CascadeSlices[cascadeIndex].projectionMatrix);

                if (!success)
                    return false;
            }

            return true;
        }

        public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
        {
            m_MainLightShadowmapTexture = ShadowUtils.GetTemporaryShadowTexture(m_ShadowmapWidth,
                    m_ShadowmapHeight, k_ShadowmapBufferBits);

            ConfigureTarget(new RenderTargetIdentifier(m_MainLightShadowmapTexture));
            ConfigureClear(ClearFlag.All, Color.black);
        }

        /// <inheritdoc/>
        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            RenderMainLightCascadeShadowmap(ref context, ref renderingData.cullResults, ref renderingData.lightData, ref renderingData.shadowData);
        }

        /// <inheritdoc/>
        public override void FrameCleanup(CommandBuffer cmd)
        {
            if (cmd == null)
                throw new ArgumentNullException("cmd");

            if (m_MainLightShadowmapTexture)
            {
                RenderTexture.ReleaseTemporary(m_MainLightShadowmapTexture);
                m_MainLightShadowmapTexture = null;
            }
            cmd.ReleaseTemporaryRT(m_TemporaryShadowMap1_Handle.id);
        }

        void Clear()
        {
            m_MainLightShadowmapTexture = null;

            for (int i = 0; i < m_MainLightShadowMatrices.Length; ++i)
                m_MainLightShadowMatrices[i] = Matrix4x4.identity;

            for (int i = 0; i < m_CascadeSplitDistances.Length; ++i)
                m_CascadeSplitDistances[i] = new Vector4(0.0f, 0.0f, 0.0f, 0.0f);

            for (int i = 0; i < m_CascadeSlices.Length; ++i)
                m_CascadeSlices[i].Clear();
        }

        void RenderMainLightCascadeShadowmap(ref ScriptableRenderContext context, ref CullingResults cullResults, ref LightData lightData, ref ShadowData shadowData)
        {
            int shadowLightIndex = lightData.mainLightIndex;
            if (shadowLightIndex == -1)
                return;

            VisibleLight shadowLight = lightData.visibleLights[shadowLightIndex];

            CommandBuffer cmd = CommandBufferPool.Get(m_ProfilerTag);
            using (new ProfilingScope(cmd, m_ProfilingSampler))
            {
                var settings = new ShadowDrawingSettings(cullResults, shadowLightIndex);

                for (int cascadeIndex = 0; cascadeIndex < m_ShadowCasterCascadesCount; ++cascadeIndex)
                {
                    var splitData = settings.splitData;
                    splitData.cullingSphere = m_CascadeSplitDistances[cascadeIndex];
                    settings.splitData = splitData;
                    Vector4 shadowBias = ShadowUtils.GetShadowBias(ref shadowLight, shadowLightIndex, ref shadowData, m_CascadeSlices[cascadeIndex].projectionMatrix, m_CascadeSlices[cascadeIndex].resolution);
                    ShadowUtils.SetupShadowCasterConstantBuffer(cmd, ref shadowLight, shadowBias);
                    ShadowUtils.RenderShadowSlice(cmd, ref context, ref m_CascadeSlices[cascadeIndex],
                        ref settings, m_CascadeSlices[cascadeIndex].projectionMatrix, m_CascadeSlices[cascadeIndex].viewMatrix);
                }

                bool softShadows = shadowLight.light.shadows == LightShadows.Soft && shadowData.supportsSoftShadows;
                CoreUtils.SetKeyword(cmd, ShaderKeywordStrings.MainLightShadows, true);
                CoreUtils.SetKeyword(cmd, ShaderKeywordStrings.MainLightShadowCascades, shadowData.mainLightShadowCascadesCount > 1);
                CoreUtils.SetKeyword(cmd, ShaderKeywordStrings.SoftShadows, softShadows);

                SetupMainLightShadowReceiverConstants(cmd, shadowLight, softShadows);
            }

            if (m_strategy == ShadowStrategy.ESM)
            {
                #region 添加 esm shadow
                RenderTextureDescriptor descriptor = m_MainLightShadowmapTexture.descriptor;
                descriptor.enableRandomWrite = true;
                descriptor.depthBufferBits = k_ShadowmapBufferBits;
                descriptor.graphicsFormat = Experimental.Rendering.GraphicsFormat.R32_SFloat;
                cmd.GetTemporaryRT(m_TemporaryShadowMap1_Handle.id, descriptor);

                /*
                // 先做esm的指数化
                cmd.SetComputeFloatParam(esmShadowComputeShader, Shader.PropertyToID("_esmConstC"), esmC);
                cmd.SetComputeTextureParam(esmShadowComputeShader, esmShadowExpKernel, Shader.PropertyToID("Input"), m_MainLightShadowmapTexture);
                cmd.SetComputeTextureParam(esmShadowComputeShader, esmShadowExpKernel, Shader.PropertyToID("Result"), m_TemporaryShadowMap1_Handle.Identifier());
                cmd.DispatchCompute(esmShadowComputeShader, esmShadowExpKernel, m_ShadowmapWidth / 8, m_ShadowmapHeight / 8, 1);

                // // 再做预滤波
                // cmd.SetComputeTextureParam(esmShadowComputeShader, esmShadowPrefilterKernel, Shader.PropertyToID("Input"), m_MainLightShadowmapTexture);
                // cmd.SetComputeTextureParam(esmShadowComputeShader, esmShadowPrefilterKernel, Shader.PropertyToID("Result"), m_TemporaryShadowMap_Handle.Identifier());
                // cmd.DispatchCompute(esmShadowComputeShader, esmShadowPrefilterKernel, m_ShadowmapWidth / 8, m_ShadowmapHeight / 8, 1);
                // cmd.CopyTexture(m_TemporaryShadowMap_Handle.Identifier(), m_MainLightShadowmapTexture);

                // 使用dual blur做预滤波
                descriptor.width /= 2;
                descriptor.height /= 2;
                cmd.GetTemporaryRT(m_TemporaryShadowMap2_Handle.id, descriptor);
                cmd.SetComputeTextureParam(blurUtilsComputeShader, dualBlurDownSampleKernel, Shader.PropertyToID("Input"), m_TemporaryShadowMap1_Handle.Identifier());
                cmd.SetComputeTextureParam(blurUtilsComputeShader, dualBlurDownSampleKernel, Shader.PropertyToID("Result"), m_TemporaryShadowMap2_Handle.Identifier());
                cmd.DispatchCompute(blurUtilsComputeShader, dualBlurDownSampleKernel, m_ShadowmapWidth / 16, m_ShadowmapHeight / 16, 1);

                descriptor.width /= 2;
                descriptor.height /= 2;
                cmd.GetTemporaryRT(m_TemporaryShadowMap3_Handle.id, descriptor);
                cmd.SetComputeTextureParam(blurUtilsComputeShader, dualBlurDownSampleKernel, Shader.PropertyToID("Input"), m_TemporaryShadowMap2_Handle.Identifier());
                cmd.SetComputeTextureParam(blurUtilsComputeShader, dualBlurDownSampleKernel, Shader.PropertyToID("Result"), m_TemporaryShadowMap3_Handle.Identifier());
                cmd.DispatchCompute(blurUtilsComputeShader, dualBlurDownSampleKernel, m_ShadowmapWidth / 32, m_ShadowmapHeight / 32, 1);

                cmd.SetComputeTextureParam(blurUtilsComputeShader, dualBlurUpSampleKernel, Shader.PropertyToID("Input"), m_TemporaryShadowMap3_Handle.Identifier());
                cmd.SetComputeTextureParam(blurUtilsComputeShader, dualBlurUpSampleKernel, Shader.PropertyToID("Result"), m_TemporaryShadowMap2_Handle.Identifier());
                cmd.DispatchCompute(blurUtilsComputeShader, dualBlurUpSampleKernel, m_ShadowmapWidth / 16, m_ShadowmapHeight / 16, 1);
                cmd.ReleaseTemporaryRT(m_TemporaryShadowMap3_Handle.id);

                cmd.SetComputeTextureParam(blurUtilsComputeShader, dualBlurUpSampleKernel, Shader.PropertyToID("Input"), m_TemporaryShadowMap2_Handle.Identifier());
                cmd.SetComputeTextureParam(blurUtilsComputeShader, dualBlurUpSampleKernel, Shader.PropertyToID("Result"), m_TemporaryShadowMap1_Handle.Identifier());
                cmd.DispatchCompute(blurUtilsComputeShader, dualBlurUpSampleKernel, m_ShadowmapWidth / 8, m_ShadowmapHeight / 8, 1);
                cmd.ReleaseTemporaryRT(m_TemporaryShadowMap2_Handle.id);

                cmd.CopyTexture(m_TemporaryShadowMap1_Handle.Identifier(), m_MainLightShadowmapTexture);
                cmd.SetGlobalFloat(Shader.PropertyToID("_esmConstC"), esmC);
                 */

                cmd.SetComputeFloatParam(esmShadowComputeShader, Shader.PropertyToID("_esmConstC"), esmC);
                cmd.SetComputeTextureParam(esmShadowComputeShader, esmGuassianBlurKernel2, Shader.PropertyToID("Input"), m_MainLightShadowmapTexture);
                cmd.SetComputeTextureParam(esmShadowComputeShader, esmGuassianBlurKernel2, Shader.PropertyToID("Result"), m_TemporaryShadowMap1_Handle.Identifier());
                cmd.DispatchCompute(esmShadowComputeShader, esmGuassianBlurKernel2, m_ShadowmapWidth / 8, m_ShadowmapHeight / 8, 1);
                cmd.CopyTexture(m_TemporaryShadowMap1_Handle.Identifier(), m_MainLightShadowmapTexture);

                cmd.ReleaseTemporaryRT(m_TemporaryShadowMap1_Handle.id);

                cmd.SetGlobalFloat(Shader.PropertyToID("_esmConstC"), esmC);
                #endregion
            }
            else if (m_strategy == ShadowStrategy.VSM)
            {
                #region 添加 vsm shadow
                RenderTextureDescriptor descriptor = m_MainLightShadowmapTexture.descriptor;
                descriptor.enableRandomWrite = true;
                descriptor.depthBufferBits = k_ShadowmapBufferBits;
                descriptor.graphicsFormat = Experimental.Rendering.GraphicsFormat.R32G32_SFloat;
                cmd.GetTemporaryRT(m_TemporaryShadowMap1_Handle.id, descriptor);

                // 使用guassian blur做预滤波
                cmd.SetComputeTextureParam(vsmShadowComputeShader, vsmPrefilterKernel, Shader.PropertyToID("Input"), m_MainLightShadowmapTexture);
                cmd.SetComputeTextureParam(vsmShadowComputeShader, vsmPrefilterKernel, Shader.PropertyToID("Result"), m_TemporaryShadowMap1_Handle.Identifier());
                cmd.DispatchCompute(vsmShadowComputeShader, vsmPrefilterKernel, m_ShadowmapWidth / 8, m_ShadowmapHeight / 8, 1);
                //cmd.Blit(m_TemporaryShadowMap1_Handle.Identifier(), m_MainLightShadowmapTexture);

                cmd.SetGlobalTexture("_MainLightShadowmapTextureEx", m_TemporaryShadowMap1_Handle.Identifier());

                //cmd.ReleaseTemporaryRT(m_TemporaryShadowMap1_Handle.id);
                #endregion
            }

            context.ExecuteCommandBuffer(cmd);
            CommandBufferPool.Release(cmd);
        }

        void SetupMainLightShadowReceiverConstants(CommandBuffer cmd, VisibleLight shadowLight, bool softShadows)
        {
            Light light = shadowLight.light;

            int cascadeCount = m_ShadowCasterCascadesCount;
            for (int i = 0; i < cascadeCount; ++i)
                m_MainLightShadowMatrices[i] = m_CascadeSlices[i].shadowTransform;

            // We setup and additional a no-op WorldToShadow matrix in the last index
            // because the ComputeCascadeIndex function in Shadows.hlsl can return an index
            // out of bounds. (position not inside any cascade) and we want to avoid branching
            Matrix4x4 noOpShadowMatrix = Matrix4x4.zero;
            noOpShadowMatrix.m22 = (SystemInfo.usesReversedZBuffer) ? 1.0f : 0.0f;
            for (int i = cascadeCount; i <= k_MaxCascades; ++i)
                m_MainLightShadowMatrices[i] = noOpShadowMatrix;

            float invShadowAtlasWidth = 1.0f / m_ShadowmapWidth;
            float invShadowAtlasHeight = 1.0f / m_ShadowmapHeight;
            float invHalfShadowAtlasWidth = 0.5f * invShadowAtlasWidth;
            float invHalfShadowAtlasHeight = 0.5f * invShadowAtlasHeight;
            float softShadowsProp = softShadows ? 1.0f : 0.0f;
            cmd.SetGlobalTexture(m_MainLightShadowmap.id, m_MainLightShadowmapTexture);
            cmd.SetGlobalMatrixArray(MainLightShadowConstantBuffer._WorldToShadow, m_MainLightShadowMatrices);
            cmd.SetGlobalVector(MainLightShadowConstantBuffer._ShadowParams, new Vector4(light.shadowStrength, softShadowsProp, 0.0f, 0.0f));

            if (m_ShadowCasterCascadesCount > 1)
            {
                cmd.SetGlobalVector(MainLightShadowConstantBuffer._CascadeShadowSplitSpheres0,
                    m_CascadeSplitDistances[0]);
                cmd.SetGlobalVector(MainLightShadowConstantBuffer._CascadeShadowSplitSpheres1,
                    m_CascadeSplitDistances[1]);
                cmd.SetGlobalVector(MainLightShadowConstantBuffer._CascadeShadowSplitSpheres2,
                    m_CascadeSplitDistances[2]);
                cmd.SetGlobalVector(MainLightShadowConstantBuffer._CascadeShadowSplitSpheres3,
                    m_CascadeSplitDistances[3]);
                cmd.SetGlobalVector(MainLightShadowConstantBuffer._CascadeShadowSplitSphereRadii, new Vector4(
                    m_CascadeSplitDistances[0].w * m_CascadeSplitDistances[0].w,
                    m_CascadeSplitDistances[1].w * m_CascadeSplitDistances[1].w,
                    m_CascadeSplitDistances[2].w * m_CascadeSplitDistances[2].w,
                    m_CascadeSplitDistances[3].w * m_CascadeSplitDistances[3].w));
            }

            if (softShadows)
            {
                if (m_SupportsBoxFilterForShadows)
                {
                    cmd.SetGlobalVector(MainLightShadowConstantBuffer._ShadowOffset0,
                        new Vector4(-invHalfShadowAtlasWidth, -invHalfShadowAtlasHeight, 0.0f, 0.0f));
                    cmd.SetGlobalVector(MainLightShadowConstantBuffer._ShadowOffset1,
                        new Vector4(invHalfShadowAtlasWidth, -invHalfShadowAtlasHeight, 0.0f, 0.0f));
                    cmd.SetGlobalVector(MainLightShadowConstantBuffer._ShadowOffset2,
                        new Vector4(-invHalfShadowAtlasWidth, invHalfShadowAtlasHeight, 0.0f, 0.0f));
                    cmd.SetGlobalVector(MainLightShadowConstantBuffer._ShadowOffset3,
                        new Vector4(invHalfShadowAtlasWidth, invHalfShadowAtlasHeight, 0.0f, 0.0f));
                }

                // Currently only used when !SHADER_API_MOBILE but risky to not set them as it's generic
                // enough so custom shaders might use it.
                cmd.SetGlobalVector(MainLightShadowConstantBuffer._ShadowmapSize, new Vector4(invShadowAtlasWidth,
                    invShadowAtlasHeight,
                    m_ShadowmapWidth, m_ShadowmapHeight));
            }
        }
    };
}
